#define TAG "record"
#include <stdarg.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <alsa/asoundlib.h>  
#include <sched.h>
#include <pthread.h>
#include "log.h"
#include "record.h"
#include "AudioQueue.h"
#include "../util/util.h"
#define ALSA_PCM_NEW_HW_PARAMS_API  
#define DOWN_RESAMPLER   1


/* ------------------------------------------------------------------------
** Defines  alsa录音相关的参数
** ------------------------------------------------------------------------ */
#define RECORD_PCM_FORMAT  SND_PCM_FORMAT_S16_LE
#define RECORD_PCM_CHANNEL 8  //通道号
#define RECORD_PCM_FRAMES  512
#define RECORD_PCM_RATE    16000
#define RECORD_PCM_DEVICE  "hw:%d,0" // 声卡设备号，需要根据实际的设备进行修改
#define RECORD_QUEUE_BUFF_MULTIPLE 30


/* ------------------------------------------------------------------------
** Types
** ------------------------------------------------------------------------ */
typedef struct _RecordData{
	snd_pcm_t *handle;
	audio_queue_t *queue;
	char *queue_buff;
	char *buffer;
	int buff_size;
	snd_pcm_uframes_t frames;
	record_audio_fn cb;
	pthread_t tid_pcm_read;
	pthread_t tid_queue_read;
	int runing;
}RecordData;


/**
 *  音频格式转换，原始数据的音频是： 16K，16bit，8通道，需要转换为：16K，32bit，6通道
 *  
 * */
static void add_channel_index_16_32(char *in_data,unsigned int in_data_len,char *out_data,int channels)
{
	int j= 0 ;
	while (j < (in_data_len / 16)) {
		out_data[24 * j + 0] = 0x00;
		out_data[24 * j + 1] = 0x01;
		out_data[24 * j + 2] = in_data[16 * j + 0];
		out_data[24 * j + 3] =  in_data[16 * j + 1];

		out_data[24 * j + 4] = 0x00;
		out_data[24 * j + 5] = 0x02;
		out_data[24 * j + 6] = in_data[16 * j + 2];
		out_data[24 * j + 7] = in_data[16 * j + 3];

		out_data[24 * j + 8] = 0x00;
		out_data[24 * j + 9] = 0x03;
		out_data[24 * j + 10] = in_data[16 * j + 4];
		out_data[24 * j + 11] =  in_data[16 * j + 5];

		out_data[24 * j + 12] = 0x00;
		out_data[24 * j + 13] = 0x04;
		out_data[24 * j + 14] = in_data[16 * j + 6];
		out_data[24 * j + 15] =  in_data[16 * j + 7];

		//通道5--》ref1
		out_data[24 * j + 16] = 0x00;
		out_data[24 * j + 17] = 0x05;
		out_data[24 * j + 18] = in_data[16 * j + 12];
		out_data[24 * j + 19] = in_data[16 * j + 13];

		//通道6 --》 ref2
		out_data[24 * j + 20] = 0x00;
		out_data[24 * j + 21] = 0x06;
		out_data[24 * j + 22] = in_data[16 * j + 14];
		out_data[24 * j + 23] = in_data[16 * j + 15];

		j++;
	}	
}

/**
 *  录音数据读取队列
 * */
static void* QueueReadThread(void* param)
{
	RecordData *record = (RecordData *)param;
	int readLen = 0;
	int ret;
	
	cpu_set_t mask;
	CPU_ZERO(&mask);
	CPU_SET(0,&mask);
	ret = sched_setaffinity(0, sizeof(mask), &mask);
	LOGD("QueueReadThread sched_setaffinity return = %d ", ret);
	
	LOGD("QueueReadThread start");
	int frameLen = record->buff_size ;//* 3/2 ;
	while(record->runing){
		char *data_buff = NULL;
		readLen = queue_read_ex(record->queue, &data_buff, frameLen, frameLen);
		// LOGE("read_len = %d ,  frameLen  = %d ", readLen , frameLen);
		//readLen = queue_read(record->queue, &data_buff);
		if (readLen <= 0){
			//LOGW("queue_read readLen = 0 ");
			usleep(16 * 1000);
			continue;
		}
		record->cb(data_buff, readLen);
		free(data_buff);
	}
	LOGD("QueueReadThread end \n");
	return NULL;
}

/**
 *   录音数据读取线程
 * 
 **/
static void* RecordThread(void* param)
{
	RecordData *record = (RecordData *)param;
	int ret = 0,i = 0,j = 0;

	cpu_set_t mask;
	CPU_ZERO(&mask);
	CPU_SET(0,&mask);
	ret = sched_setaffinity(0, sizeof(mask), &mask);
	LOGD("RecordThread sched_setaffinity return = %d ", ret);
	LOGD("RecordThread start ");

	// 8ch*2   :  6ch*4
	int    new_buffer_len =  record->buff_size ;//* 3/2 ;
	char *new_buffer = (char *) malloc(new_buffer_len);
	printf("raw size = %d , new_buffer size = %d \n", record->buff_size , new_buffer_len );
	while (record->runing){
		ret = snd_pcm_readi(record->handle, record->buffer, record->frames); 
		//LOGV("RecordThread snd_pcm_readi return = %d ", ret);
		if (ret == -EPIPE) {
			/* EPIPE means overrun */  
			LOGE("RecordThread overrun occurred ");
			snd_pcm_prepare(record->handle);
			continue;
		} else if (ret < 0) {
			LOGE("RecordThread error from read: errno = %d,%s",errno,snd_strerror(ret)); 
			//return NULL;
			continue;
		} else if (ret != (int)record->frames) {
			LOGE("RecordThread short read, read %d frames ", ret);
			continue;
		}

		//add_channel_index_16_32(record->buffer,record->buff_size,new_buffer,RECORD_PCM_CHANNEL);

		if(queue_write(record->queue, record->buffer,  record->buff_size) < 0){
			LOGE("RecordThread write error "); 
			usleep(30*1000);
			queue_write(record->queue, record->buffer, record->buff_size);
		}
	}

	if(new_buffer != NULL){
		free(new_buffer);
		new_buffer = NULL;
	}
	LOGD("RecordThread end ");
	return NULL;
}

/**
 *  开始录音
 * 
 * */
int record_start(REACORD_HANDLE *record_hd, record_audio_fn audio_cb)
{
	int rc;
	pthread_attr_t thread_attr;
	struct sched_param thread_param;
	snd_pcm_hw_params_t *params; 
	int dir;  
	snd_pcm_uframes_t frames;  
	char *buffer;  
    RecordData *record = NULL;
	unsigned int val = RECORD_PCM_RATE;
		
	int ret =0;
	if (NULL == record_hd || NULL == audio_cb){
		return -1;
	}

	int card = get_usb_card_num();
	if (card < 0)
	{
		LOGE("cannot find usb card");
		return -1 ;
	}

	record = (RecordData *)malloc(sizeof(RecordData));
	LOGD("record_start record :%p\n", record);
	if (NULL == record){
		return -1;
	}
	memset(record, 0, sizeof(RecordData));
	record->cb = audio_cb;

	char temp_device_name[100] = {0};
	sprintf(temp_device_name , RECORD_PCM_DEVICE , card);

	/* Open PCM device for recording (capture). */  
	rc = snd_pcm_open(&record->handle, temp_device_name ,SND_PCM_STREAM_CAPTURE, 0);  
	if (rc < 0) {
		LOGE("unable to open pcm device: %s ",  snd_strerror(rc));  
		ret = -1;  
		goto error;
	}  
	/* Allocate a hardware parameters object. */  
	snd_pcm_hw_params_alloca(&params);  

	/* Fill it in with default values. */  
	snd_pcm_hw_params_any(record->handle, params);  

	/* Set the desired hardware parameters. */  
	/* Interleaved mode */  
	snd_pcm_hw_params_set_access(record->handle, params,  
		SND_PCM_ACCESS_RW_INTERLEAVED);  

	/* Signed 16-bit little-endian format */  
	snd_pcm_hw_params_set_format(record->handle, params,  
		RECORD_PCM_FORMAT);

	/* Two channels (stereo) */  
	snd_pcm_hw_params_set_channels(record->handle, params, RECORD_PCM_CHANNEL);  

	/* 44100 bits/second sampling rate (CD quality) */  
	snd_pcm_hw_params_set_rate_near(record->handle, params,  &val, &dir);  

	/* Set period size to 32 frames. */  
	frames = RECORD_PCM_FRAMES;  
	snd_pcm_hw_params_set_period_size_near(record->handle,  params, &frames, &dir);  
	
	/* Write the parameters to the driver */  
	rc = snd_pcm_hw_params(record->handle, params);  
	if (rc < 0) {  
		LOGE("unable to set hw parameters: %s\n",snd_strerror(rc));  
		ret = -1;
		goto exit;
	}  
	/* Use a buffer large enough to hold one period */  
	snd_pcm_hw_params_get_period_size(params,  &frames, &dir); 
	record->frames = frames;
	record->buff_size = frames * snd_pcm_format_physical_width(RECORD_PCM_FORMAT) * RECORD_PCM_CHANNEL/8; //16bit 8channels
	LOGD("frames = %d,record->buff_size = %d ", frames, record->buff_size);
	record->buffer = (char *) malloc(record->buff_size);  
	if (NULL == record->buffer){
		ret = -1;
		goto error;
	}

	record->queue_buff = (char *) malloc(sizeof(audio_queue_t) + record->buff_size * RECORD_QUEUE_BUFF_MULTIPLE + 1);
	if (NULL == record->queue_buff){
		ret = -1;
		goto error;
	}
	record->queue = queue_init(record->queue_buff, record->buff_size * RECORD_QUEUE_BUFF_MULTIPLE + 1);

	pthread_attr_init(&thread_attr);
	pthread_attr_setschedpolicy(&thread_attr, SCHED_RR);
	thread_param.sched_priority = sched_get_priority_max(SCHED_RR);
	pthread_attr_setschedparam(&thread_attr, &thread_param);
	record->runing = 1;
	
	ret = pthread_create(&record->tid_pcm_read, &thread_attr, RecordThread, (void*)record);
	
	if (0 != ret){
		LOGE("tid_pcm_read create failed ret = %d ", ret);
		goto error;
	}
	
	ret = pthread_create(&record->tid_queue_read, &thread_attr, QueueReadThread, (void*)record);
	if (0 != ret){
		LOGE("tid_queue_read create failed ret = %d ", ret);
		goto error;
	}
	pthread_attr_destroy(&thread_attr);
	*record_hd = record;
	goto exit;

error:
	LOGE("record start error = %d\n", ret);
	record_stop(record);

exit:
	LOGD("record start out ret = %d\n", ret);
	return ret;
}

/**
 *  停止录音
 * 
 * */
void record_stop(REACORD_HANDLE record_hd)
{
	RecordData *record = (RecordData*)record_hd;
	LOGD("record_stop in record:%p ", record);
	if (NULL != record){
		record->runing = 0;
		if(record->tid_queue_read != NULL){
			pthread_join(record->tid_queue_read, NULL);
			record->tid_queue_read = NULL;
		}
		
		if(record->tid_pcm_read != NULL){
			pthread_join(record->tid_pcm_read, NULL);
			record->tid_pcm_read = NULL;
		}

		if(record->handle != NULL){
			snd_pcm_drain(record->handle);  
			snd_pcm_close(record->handle);
			record->handle = NULL;
		}

		if (NULL != record->queue){
			queue_destroy(record->queue);
			record->queue = NULL;
		}
		
		if (NULL != record->queue_buff){
			free(record->queue_buff);
			record->queue_buff = NULL;
		}
		
		if (NULL != record->buffer){
			free(record->buffer);
			record->buffer = NULL;
		}
		free(record);
		record = NULL;
	}
	LOGD("record_stop out ");
}
